package com.example.user.service.user.impl;

import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.example.common.core.constants.CacheConstants;
import com.example.common.core.constants.Constants;
import com.example.common.core.domain.R;
import com.example.common.core.domain.RedisUser;
import com.example.common.core.enums.ResultCode;
import com.example.common.core.enums.UserIdentity;
import com.example.common.core.utils.ThreadLocalUtil;
import com.example.common.message.service.AliSmsService;
import com.example.common.redis.service.RedisService;
import com.example.common.security.exception.ServiceException;
import com.example.common.security.service.TokenService;
import com.example.user.domain.user.User;
import com.example.user.domain.user.dto.UserDTO;
import com.example.user.domain.user.dto.UserUpdateDTO;
import com.example.user.domain.user.vo.LoginUserVO;
import com.example.user.domain.user.vo.UserVO;
import com.example.user.manager.UserCacheManager;
import com.example.user.mapper.user.UserMapper;
import com.example.user.service.user.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Service
@RefreshScope
public class UserServiceImpl implements UserService {
    @Autowired
    private RedisService redisService;

    @Autowired
    private TokenService tokenService;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private AliSmsService aliSmsService;

    @Autowired
    private UserCacheManager userCacheManager;

    @Value("${sms.code-expiration:5}")
    public  Long PHONE_CODE_REFRESH_TIME;

    @Value("${sms.send-limit:10}")
    private Integer sendLimit;

    @Value("${sms.is-send:false}")
    private Boolean isSend; //发送开关

    @Value("${jwt.secret}")
    private String secret;

    @Value("${file.oss.downloadUrl}")
    private String downloadUrl;
    @Override
    public Boolean sendCode(UserDTO userDTO) {
        if (!checkPhone(userDTO.getPhone())) {
            throw new ServiceException(ResultCode.FAILED_USER_PHONE);
        }
        Long expireTime = redisService.getExpire(getPhoneCodeKey(userDTO.getPhone()),TimeUnit.SECONDS);
        if (expireTime!= null && PHONE_CODE_REFRESH_TIME*60 - expireTime < 60) {
            throw new ServiceException(ResultCode.FAILED_FREQUENT_REQUEST);
        }
        String codeTimeKey = getCodeTimeKey(userDTO.getPhone());
        Long sendTimes = redisService.getCacheObject(codeTimeKey, Long.class);
        if (sendTimes!= null && sendTimes >= sendLimit) {
            throw new ServiceException(ResultCode.FAILED_TIME_LIMIT);
        }
        String code =isSend ? RandomUtil.randomNumbers(6) : CacheConstants.DEFAULT_CODE;
        if (isSend) {
            if (!aliSmsService.sendMobileCode(userDTO.getPhone(), code)) {
                throw new ServiceException(ResultCode.FAILED_SEND_CODE);
            }
        }
        redisService.setCacheObject(getPhoneCodeKey(userDTO.getPhone()), code, PHONE_CODE_REFRESH_TIME, TimeUnit.MINUTES);
        redisService.increment(codeTimeKey);
        if (sendTimes == null) {
            long seconds = ChronoUnit.SECONDS.between(LocalDateTime.now(),LocalDateTime.now().plusDays(1).withHour(0).withMinute(0).withSecond(0).withNano(0));
            redisService.expire(codeTimeKey, seconds, TimeUnit.SECONDS);
        }
        return  redisService.hasKey(getPhoneCodeKey(userDTO.getPhone())) && redisService.hasKey(codeTimeKey);

    }

    @Override
    public R<String> login(UserDTO userDTO) {
        checkCode(userDTO);
        User user = userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getPhone, userDTO.getPhone()));
        if (user == null) {
            user = new User();
            user.setPhone(userDTO.getPhone());
            userMapper.insert(user);
        }
        String token = tokenService.createToken(user.getUserId(), secret, UserIdentity.USER.getValue(), user.getNickName(),user.getHeadImage());
        return R.ok(token);

    }

    @Override
    public boolean exitLogin(String token) {
        return tokenService.exitLogin(token,secret);
    }

    @Override
    public R<LoginUserVO> info(String token) {
        RedisUser redisUser = tokenService.getRedisUser(token,secret);
        if (redisUser == null) {
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        LoginUserVO loginUserVO = new LoginUserVO();
        loginUserVO.setNickName(redisUser.getUserNick());
        if (redisUser.getHeadImage() != null) {
            loginUserVO.setHeadImage(downloadUrl+redisUser.getHeadImage());
        }
        return  R.ok(loginUserVO);
    }

    @Override
    public R<UserVO> detail() {
        Long userId = ThreadLocalUtil.get(Constants.USER_ID, Long.class);
        if (userId == null) {
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        UserVO userVO = userCacheManager.getUserById(userId);
        if (userVO == null) {
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        if (userVO.getHeadImage() != null) {
            userVO.setHeadImage(downloadUrl+userVO.getHeadImage());
        }
        return R.ok(userVO);
    }

    @Override
    public int edit(UserUpdateDTO userUpdateDTO) {
        User user = getUser();
        if (user == null) {
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        user.setNickName(userUpdateDTO.getNickName());
        user.setSex(userUpdateDTO.getSex());
        user.setSchoolName(userUpdateDTO.getSchoolName());
        user.setMajorName(userUpdateDTO.getMajorName());
        user.setPhone(userUpdateDTO.getPhone());
        user.setEmail(userUpdateDTO.getEmail());
        user.setWechat(userUpdateDTO.getWechat());
        user.setIntroduce(userUpdateDTO.getIntroduce());
        //更新用户缓存
        userCacheManager.refreshUser(user);
        tokenService.refreshLoginUser(user.getNickName(),user.getHeadImage(),
                ThreadLocalUtil.get(Constants.USER_ID, Long.class));
        return userMapper.updateById(user);
    }



    @Override
    public int updateHeadImage(UserUpdateDTO userUpdateDTO) {
        User user = getUser();
        if (user == null) {
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        user.setHeadImage(userUpdateDTO.getHeadImage());
        userCacheManager.refreshUser(user);
        tokenService.refreshLoginUser(user.getNickName(),user.getHeadImage(),
                ThreadLocalUtil.get(Constants.USER_ID, Long.class));
        return userMapper.updateById(user);
    }

    private void checkCode(UserDTO userDTO) {
        String phoneKey = getPhoneCodeKey(userDTO.getPhone());
        String code = redisService.getCacheObject(phoneKey, String.class);
        if (StrUtil.isEmpty(userDTO.getCode())) {
            throw new ServiceException(ResultCode.FAILED_INVALID_CODE);
        } else if (!userDTO.getCode().equals(code)) {
            throw new ServiceException(ResultCode.FAILED_ERROR_CODE);
        }
        redisService.deleteObject(phoneKey);
    }

    private static boolean checkPhone(String phone) {
        Pattern pattern = Pattern.compile("^1[3-9]\\d{9}$");
        Matcher matcher = pattern.matcher(phone);
        return matcher.matches();
    }

    private String getPhoneCodeKey(String phone) {
        return CacheConstants.PHONE_CODE_KEY + phone;
    }
    private String getCodeTimeKey(String phone) {
        return CacheConstants.CODE_TIME_KEY + phone;
    }
    private User getUser() {
        Long userId = ThreadLocalUtil.get(Constants.USER_ID, Long.class);
        if (userId == null) {
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        User user = userMapper.selectById(userId);
        return user;
    }
}
